#include "main.h"
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include "dev/tty.h"
#include "fs/file.h"
#include "lib_syscall.h"

static cli_t cli;
static const char* promot = "[raibow]# ";

void show_promot(void) {
    printf("%s", cli.promot);
    fflush(stdout);
}

void show_help() {
    const cli_cmd_t* start = cli.cmd_start;
    printf("Built-in commands supported by the system:\n");
    printf("+---------------------------------------------+\n");
    while (start < cli.cmd_end) {
        printf("|%-7s| %-36s|\n", start->name, start->usage);
        if (start)
            start++;
        if (start != cli.cmd_end)
            printf("|---------------------------------------------|\n");
        else
            printf("+---------------------------------------------+\n");
    }
}

int do_help(int argc, char** argv) {
    show_help();
    return 0;
}

static int do_clear(int argc, char** argv) {
    printf("%s", ESC_CLEAR_SCREEN);
    printf("%s", ESC_MOVE_CURSOR(0, 0));
    return 0;
}

static int do_echo(int argc, char** argv) {
    // 只有一个参数，需要先手动输入，再输出
    if (argc == 1) {
        char msg_buf[128];

        fgets(msg_buf, sizeof(msg_buf), stdin);
        msg_buf[sizeof(msg_buf) - 1] = '\0';
        puts(msg_buf);
        return 0;
    }

    // https://www.cnblogs.com/yinghao-liu/p/7123622.html
    // optind是下一个要处理的元素在argv中的索引
    // 当没有选项时，变为argv第一个不是选项元素的索引。
    int count = 1;  // 缺省只打印一次
    int ch;
    while ((ch = getopt(argc, argv, "n:h")) != -1) {
        switch (ch) {
            case 'h':
                puts("echo echo any message");
                puts("Usage: echo [-n count] msg");
                optind = 1;  // getopt需要多次调用，需要重置
                return 0;
            case 'n':
                count = atoi(optarg);
                break;
            case '?':
                if (optarg) {
                    fprintf(stderr, "Unknown option: -%s\n", optarg);
                }
                optind = 1;  // getopt需要多次调用，需要重置
                return -1;
            default:
                break;
        }
    }

    // 索引已经超过了最后一个参数的位置，意味着没有传入要发送的信息
    if (optind > argc - 1) {
        fprintf(stderr, "Message is empty \n");
        optind = 1;  // getopt需要多次调用，需要重置
        return -1;
    }

    // 循环打印消息
    char* msg = argv[optind];
    for (int i = 0; i < count; i++) {
        puts(msg);
    }
    optind = 1;  // getopt需要多次调用，需要重置
    return 0;
}

void show_ls(struct dirent* entry, int line_mode, int num) {
    char is_file = entry->type == FILE_DIR ? 'd' : 'f';

    if (line_mode) {
        if (num % 5 == 0) {
            if (num)
                printf("\n%-15s", entry->name);
            else
                printf("%-15s", entry->name);
        } else {
            printf("%-15s", entry->name);
        }
    } else {
        // printf(" %c    root    %7d    %s\n", is_file, entry->size,
        // entry->name);
        printf(" %c    root    %-7d    %-7s\n", is_file, entry->size,
               entry->name);
    }
}

static int do_ls(int argc, char** argv) {
    int line_mode = 0;
    int ch;
    while ((ch = getopt(argc, argv, "lh")) != -1) {
        switch (ch) {
            case 'h':
                puts("List files");
                puts("Usage: ls [-l]");
                optind = 1;  // getopt需要多次调用，需要重置
                return 0;
            case 'l':
                line_mode = 1;
                break;
            case '?':
                if (optarg) {
                    fprintf(stderr, "Unknown option: -%s\n", optarg);
                }
                optind = 1;  // getopt需要多次调用，需要重置
                return -1;
            default:
                break;
        }
    }

    DIR* p_dir = opendir("temp");
    if (p_dir == NULL) {
        printf("open dir failed.");
        return -1;
    }

    struct dirent* entry;
    int cnt = 0;
    while ((entry = readdir(p_dir)) != NULL) {
        strlwr(entry->name);
        show_ls(entry, line_mode, cnt++);
    }

    if (line_mode)
        printf("\n");

    optind = 1;  // getopt需要多次调用，需要重置
    closedir(p_dir);
    return 0;
}

static int do_less(int argc, char** argv) {
    int line_mode = 0;

    // https://www.cnblogs.com/yinghao-liu/p/7123622.html
    // 当没有选项时，变为argv第一个不是选项元素的索引。
    int ch;
    while ((ch = getopt(argc, argv, "lh")) != -1) {
        switch (ch) {
            case 'h':
                puts("show file content");
                puts("Usage: less [-l] file");
                optind = 1;  // getopt需要多次调用，需要重置
                return 0;
            case 'l':
                line_mode = 1;
                break;
            case '?':
                if (optarg) {
                    fprintf(stderr, "Unknown option: -%s\n", optarg);
                }
                optind = 1;  // getopt需要多次调用，需要重置
                return -1;
            default:
                break;
        }
    }

    // 索引已经超过了最后一个参数的位置，意味着没有传入要发送的信息
    if (optind > argc - 1) {
        fprintf(stderr, "no file\n");
        optind = 1;  // getopt需要多次调用，需要重置
        return -1;
    }

    // 打开待预览文件
    FILE* file = fopen(argv[optind], "r");
    if (file == NULL) {
        fprintf(stderr, "open file failed. %s", argv[optind]);
        optind = 1;
        return -1;
    }

    char* buf = (char*)malloc(255);
    if (line_mode == 0) {
        while (fgets(buf, 255, file) != NULL) {
            fputs(buf, stdout);
        }
    } else {
        setvbuf(stdin, NULL, _IONBF, 0);  // 关闭行缓存
        ioctl(0, TTY_CMD_ECHO, 0, 0);     // 关闭标准输入流回显
        while (1) {
            char* b = fgets(buf, 255, file);
            if (b == NULL) {
                break;
            }

            fputs(buf, stdout);

            int ch;
            while ((ch = fgetc(stdin)) != 'n') {
                if (ch == 'q') {
                    goto less_quit;
                }
            }
        }

    less_quit:
        setvbuf(stdin, NULL, _IOLBF, BUFSIZ);  // 打开行缓存
        ioctl(0, TTY_CMD_ECHO, 1, 0);          // 打开标准输入流回显
    }

    free(buf);
    fclose(file);
    optind = 1;  // getopt需要多次调用，需要重置
    return 0;
}

static int do_cp(int argc, char** argv) {
    if (argc < 3) {
        fprintf(stderr, "no [src] or no [dest] file");
        return -1;
    }

    FILE *from, *to;
    from = fopen(argv[1], "rb");
    to = fopen(argv[2], "wb");

    if (!from || !to) {
        fprintf(stderr, "open file failed");
        goto cp_failed;
    }

    char* buf = (char*)malloc(255);
    int size;
    while ((size = fread(buf, 1, 255, from)) > 0) {
        fwrite(buf, 1, size, to);
    }

    free(buf);
    fclose(from);
    fclose(to);

    return 0;
cp_failed:
    if (from) {
        fclose(from);
    }

    if (to) {
        fclose(to);
    }

    return -1;
}

static int do_rm(int argc, char** argv) {
    if (argc < 2) {
        fprintf(stderr, "no file");
        return -1;
    }

    int err = unlink(argv[1]);
    if (err < 0) {
        fprintf(stderr, "rm file failed: %s", argv[1]);
        return err;
    }

    return 0;
}

static int do_exit(int argc, char** argv) {
    exit(0);
    return 0;
}

static const cli_cmd_t cmd_list[] = {
    {
        .name = "help",
        .usage = "display helpful information",
        .do_func = do_help,
    },
    {
        .name = "clear",
        .usage = "clear the terminal screen",
        .do_func = do_clear,
    },
    {
        .name = "ls",
        .usage = "list directory contents",
        .do_func = do_ls,
    },
    {
        .name = "less",
        .usage = "show file in linemode",
        .do_func = do_less,
    },
    {
        .name = "cp",
        .usage = "copy files from source to the target",
        .do_func = do_cp,
    },
    {
        .name = "rm",
        .usage = "remove directory entries and files",
        .do_func = do_rm,
    },
    {
        .name = "quit",
        .usage = "quit from shell",
        .do_func = do_exit,
    },
};

static void cli_init(const char* promot, const cli_cmd_t* cmd_list, int size) {
    cli.promot = promot;
    memset(cli.curr_input, 0, CLI_IPUT_SIZE);
    cli.cmd_start = cmd_list;
    cli.cmd_end = cmd_list + size;
}

static const cli_cmd_t* find_builtin(const char* name) {
    for (const cli_cmd_t* cmd = cli.cmd_start; cmd < cli.cmd_end; cmd++) {
        if (strcmp(cmd->name, name) != 0) {
            continue;
        }

        return cmd;
    }

    return (const cli_cmd_t*)0;
}

static void run_builtin(const cli_cmd_t* cmd, int argc, char** argv) {
    int ret = cmd->do_func(argc, argv);
    if (ret < 0) {
        fprintf(stderr, ESC_COLOR_ERROR "error: %d\n" ESC_COLOR_DEFUALT, ret);
    }
}

static void run_exec_file(const char* path, int argc, char** argv) {
    int pid = fork();
    if (pid < 0) {
        fprintf(stderr, "fork failed %s", path);
    } else if (pid == 0) {
        int err = execve(path, argv, (char* const*)0);
        if (err < 0) {
            fprintf(stderr, "exec failed: %s", path);
        }
        exit(-1);
    } else {
        int status;
        int pid = wait(&status);  // 父进程等待子进程的返回
        // fprintf(stderr, "cmd %s result: %d, pid=%d\n", path, status, pid);
    }
}

static const char* find_exec_path(const char* filename) {
    static char path[255];
    int fd = open(filename, 0);
    if (fd < 0) {
        // 打开失败再尝试加上文件后缀名
        sprintf(path, "%s.elf", filename);
        fd = open(path, 0);
        if (fd < 0) {
            return (const char*)0;
        }

        close(fd);
        return path;
    } else {
        close(fd);
        return filename;
    }
}

int main(int argc, char** argv) {
    open(argv[0], O_RDWR);  // int fd = 0, stdin => tty0
    dup(0);                 // int fd = 1, stdout => tty0
    dup(0);                 // int fd = 2

    cli_init(promot, cmd_list, sizeof(cmd_list) / sizeof(cmd_list[0]));

    printf("\n");
    printf("+-----------------------------+\n");
    printf("|                             |\n");
    printf("|           W  O  S           |\n");
    printf("|       Author: rainbow       |\n");
    printf("|       Version: 1.0.0        |\n");
    printf("|                             |\n");
    printf("+-----------------------------+\n");
    printf("\n");

    while (1) {
        show_promot();
        char* str = fgets(cli.curr_input, CLI_IPUT_SIZE, stdin);
        if (!str) {
            continue;
        }

        char* cr = strchr(cli.curr_input, '\n');
        if (cr) {
            *cr = '\0';
        }
        cr = strchr(cli.curr_input, '\r');
        if (cr) {
            *cr = '\0';
        }

        int argc = 0;
        char* argv[CLI_MAX_AGR_COUNT];
        memset(argv, 0, sizeof(argv));

        const char* space = " ";
        char* token = strtok(cli.curr_input, space);
        while (token && argc < CLI_MAX_AGR_COUNT) {
            argv[argc++] = token;
            token = strtok(NULL, space);
        }
        if (argc == 0) {
            continue;
        }

        // 处理内置命令
        const cli_cmd_t* cmd = find_builtin(argv[0]);
        if (cmd) {
            run_builtin(cmd, argc, argv);
            continue;
        }

        // 执行位于磁盘上的文件
        const char* path = find_exec_path(argv[0]);
        if (path) {
            run_exec_file("", argc, argv);
            continue;
        }

        fprintf(stderr,
                ESC_COLOR_ERROR "Unknown command: %s\n" ESC_COLOR_DEFUALT,
                cli.curr_input);
    }

    return 0;
}